﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using GurLoader;
using GurLoader.Service;
using Moq;
using NUnit.Framework;

namespace GurLoaderWPF.Test.Service
{
    [TestFixture]
    public class DelegatingCommandProgressTests
    {
        [Test]
        public void CreateInstanceDispatcherTest()
        {
            Mock<IDispatcher> dispatcherMock = new Mock<IDispatcher>();
            var instance = new DelegatingCommandProgress(dispatcherMock.Object);

            Assert.IsNotNull(instance);
        }

        [Test]
        public void CreateInstanceCommandProgressStatusChildrenDispatcherTest()
        {
            Command command = Command.SendImages;
            int progress = 10;
            string status = "In progres...";
            List<CommandProgress> children = new List<CommandProgress>();
            Mock<IDispatcher> dispatcherMock = new Mock<IDispatcher>();
            var instance = new DelegatingCommandProgress(command, progress, status, children, dispatcherMock.Object);

            Assert.IsNotNull(instance);
        }

        [Test]
        public void CreateInstanceCommandProgressStatusDispatcherTest()
        {
            Command command = Command.SendImages;
            int progress = 10;
            string status = "In progres...";
            Mock<IDispatcher> dispatcherMock = new Mock<IDispatcher>();
            var instance = new DelegatingCommandProgress(command, progress, status, dispatcherMock.Object);

            Assert.IsNotNull(instance);
        }

        [Test]
        public void TotalProgressNoneCompleteTest()
        {
            Command command = Command.SendImages;
            int progress = 10;
            string status = "In progres...";
            List<CommandProgress> children = new List<CommandProgress>
            {
                new CommandProgress(Command.SendImage, 10, "child status"),
                new CommandProgress(Command.SendImage, 10, "child status"),
                new CommandProgress(Command.SendImage, 10, "child status"),
            };

            Mock<IDispatcher> mockDispatcher = new Mock<IDispatcher>();
            mockDispatcher.Setup(
                x => x.Invoke(It.IsAny<Delegate>(), It.IsAny<DispatcherPriority>(), It.IsAny<object[]>()))
                .Callback(
                    new Action<Delegate, DispatcherPriority, object[]>((d, dp, args) =>
                    {
                        d.GetMethodInfo().Invoke(d.Target, args);
                    }));

            var instance = new DelegatingCommandProgress(command, progress, status, children, mockDispatcher.Object);

            Assert.IsNotNull(instance);

            // progress should be 0 / 4 complete
            Assert.AreEqual(0, instance.TotalProgressPercentage);
        }

        [Test]
        public void TotalProgressSomeChildrenCompleteTest()
        {
            Command command = Command.SendImages;
            int progress = 10;
            string status = "In progres...";
            List<CommandProgress> children = new List<CommandProgress>
            {
                new CommandProgress(Command.SendImage, 100, "child status"),
                new CommandProgress(Command.SendImage, 100, "child status"),
                new CommandProgress(Command.SendImage, 10, "child status"),
            };

            Mock<IDispatcher> mockDispatcher = new Mock<IDispatcher>();
            mockDispatcher.Setup(
                x => x.Invoke(It.IsAny<Delegate>(), It.IsAny<DispatcherPriority>(), It.IsAny<object[]>()))
                .Callback(
                    new Action<Delegate, DispatcherPriority, object[]>((d, dp, args) =>
                    {
                        d.GetMethodInfo().Invoke(d.Target, args);
                    }));

            var instance = new DelegatingCommandProgress(command, progress, status, children, mockDispatcher.Object);

            Assert.IsNotNull(instance);

            // progress should be 2 / 3 complete
            Assert.AreEqual(66, instance.TotalProgressPercentage);
        }

        [Test]
        public void TotalProgressAllCompleteTest()
        {
            Command command = Command.SendImages;
            int progress = 100;
            string status = "In progres...";
            List<CommandProgress> children = new List<CommandProgress>
            {
                new CommandProgress(Command.SendImage, 100, "child status"),
                new CommandProgress(Command.SendImage, 100, "child status"),
                new CommandProgress(Command.SendImage, 100, "child status"),
            };

            Mock<IDispatcher> mockDispatcher = new Mock<IDispatcher>();
            mockDispatcher.Setup(
                x => x.Invoke(It.IsAny<Delegate>(), It.IsAny<DispatcherPriority>(), It.IsAny<object[]>()))
                .Callback(
                    new Action<Delegate, DispatcherPriority, object[]>((d, dp, args) =>
                    {
                        d.GetMethodInfo().Invoke(d.Target, args);
                    }));

            var instance = new DelegatingCommandProgress(command, progress, status, children, mockDispatcher.Object);

            Assert.IsNotNull(instance);

            // progress should be 4 / 4 complete
            Assert.AreEqual(100, instance.TotalProgressPercentage);
        }

        [Test]
        public void TotalProgressIsCompletedTest()
        {
            Command command = Command.SendImages;
            int progress = 100;
            string status = "In progres...";
            List<CommandProgress> children = new List<CommandProgress>
            {
                new CommandProgress(Command.SendImage, 50, "child status"),
                new CommandProgress(Command.SendImage, 50, "child status"),
                new CommandProgress(Command.SendImage, 50, "child status"),
            };

            Mock<IDispatcher> mockDispatcher = new Mock<IDispatcher>();
            mockDispatcher.Setup(
                x => x.Invoke(It.IsAny<Delegate>(), It.IsAny<DispatcherPriority>(), It.IsAny<object[]>()))
                .Callback(
                    new Action<Delegate, DispatcherPriority, object[]>((d, dp, args) =>
                    {
                        d.GetMethodInfo().Invoke(d.Target, args);
                    }));

            var instance = new DelegatingCommandProgress(command, progress, status, children, mockDispatcher.Object);

            Assert.IsNotNull(instance);

            Assert.Less(instance.TotalProgressPercentage, 100);

            ManualResetEvent mre = new ManualResetEvent(false);
            instance.PropertyChanged += (sender, args) =>
            {
                if (args.PropertyName.Equals("IsTotalProgressComplete"))
                {
                    mre.Set();
                }
            };

            foreach (var commandProgress in children)
            {
                commandProgress.ProgressPercentage = 100;
            }

            Assert.IsTrue(mre.WaitOne(1000*3), "Timeout waiting for IsTotalProgressComplete event");

            Assert.IsTrue(instance.IsTotalProgressComplete);
        }

        

        [Test]
        public void ChildrenChangedAddTest()
        {
            Command command = Command.SendImages;
            int progress = 20;
            string status = "In progres...";
            List<CommandProgress> children = new List<CommandProgress>
            {
                new CommandProgress(Command.SendImage, 10, "child status"),
                new CommandProgress(Command.SendImage, 10, "child status"),
                new CommandProgress(Command.SendImage, 10, "child status"),
            };

            Mock<IDispatcher> mockDispatcher = new Mock<IDispatcher>();
            mockDispatcher.Setup(
                x => x.Invoke(It.IsAny<Delegate>(), It.IsAny<DispatcherPriority>(), It.IsAny<object[]>()))
                .Callback(
                    new Action<Delegate, DispatcherPriority, object[]>((d, dp, args) =>
                    {
                        d.GetMethodInfo().Invoke(d.Target, args);
                    }));

            var instance = new DelegatingCommandProgress(command, progress, status, children, mockDispatcher.Object);

            Assert.IsNotNull(instance);

            // when the children change the delegating command progress should attach/detach event handlers such
            // that any change to the childs progress updates the parent's total progress

            CommandProgress addedChild = new CommandProgress(Command.SendImage, 10, "child status");
            int originalTotalProgress = instance.TotalProgressPercentage;
            int newTotalProgress = int.MinValue;
            
            ManualResetEvent mre = new ManualResetEvent(false);
            int callCount = 0;
            instance.PropertyChanged += (sender, e) =>
            {
                if (e.PropertyName.Equals("TotalProgressPercentage"))
                {
                    callCount++;

                    mre.Set();
                }
            };

            instance.AddChild(addedChild);
            addedChild.ProgressPercentage = 100;

            Assert.IsTrue(mre.WaitOne(1000*3), "Timeout waiting for total progress update.");
            newTotalProgress = instance.TotalProgressPercentage;

            Assert.Greater(newTotalProgress, originalTotalProgress);
            Assert.AreEqual(1, callCount);
        }

        [Test]
        public void ChildrenChangedRemoveTest()
        {
            Command command = Command.SendImages;
            int progress = 20;
            string status = "In progres...";
            List<CommandProgress> children = new List<CommandProgress>
            {
                new CommandProgress(Command.SendImage, 20, "child status"),
                new CommandProgress(Command.SendImage, 100, "child status"),
                new CommandProgress(Command.SendImage, 100, "child status"),
            };

            Mock<IDispatcher> mockDispatcher = new Mock<IDispatcher>();
            mockDispatcher.Setup(
                x => x.Invoke(It.IsAny<Delegate>(), It.IsAny<DispatcherPriority>(), It.IsAny<object[]>()))
                .Callback(
                    new Action<Delegate, DispatcherPriority, object[]>((d, dp, args) =>
                    {
                        d.GetMethodInfo().Invoke(d.Target, args);
                    }));

            var instance = new DelegatingCommandProgress(command, progress, status, children, mockDispatcher.Object);

            Assert.IsNotNull(instance);

            // when the children change the delegating command progress should attach/detach event handlers such
            // that any change to the childs progress updates the parent's total progress

            CommandProgress removedChild = children.ElementAt(0);
            int originalTotalProgress = instance.TotalProgressPercentage;
            Assert.AreEqual(66, originalTotalProgress);
            int newTotalProgress = int.MinValue;

            ManualResetEvent mre = new ManualResetEvent(false);
            int callCount = 0;
            instance.PropertyChanged += (sender, e) =>
            {
                if (e.PropertyName.Equals("TotalProgressPercentage"))
                {
                    callCount++;
                    mre.Set();
                }
            };

            instance.RemoveChild(removedChild);
            removedChild.ProgressPercentage = 10;

            newTotalProgress = instance.TotalProgressPercentage;

            // removed the incomplete one, so new progress should be greater ( 100 )
            Assert.Greater(newTotalProgress, originalTotalProgress);
            Assert.AreEqual(100, newTotalProgress);
            Assert.AreEqual(0, callCount);
        }
    }
}
